﻿using System;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace vm.Aspects.Policies.Tests
{
    /// <summary>
    /// Summary description for CodeAccessSecurityCallHandlerTest
    /// </summary>
    [TestClass]
    public class CodeAccessSecurityCallHandlerTest
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        //[TestInitialize()]
        //public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        interface ITested
        {
            string UnprotectedMethod();

            [TestPermission(SecurityAction.Demand, Unrestricted=true)]
            string UnrestrictedMethod();

            [TestPermission(SecurityAction.Demand, Permitted=true)]
            string Permitted();

            [TestPermission(SecurityAction.Demand, Permitted=false)]
            string NotPermitted();

            string LocallyProtected();
        }

        class Tested : ITested
        {
            string _called;

            public string Called
            {
                get { return _called; }
            }

            #region ITested Members

            public string UnprotectedMethod()
            {
                return _called = "UnprotectedMethod";
            }

            public string UnrestrictedMethod()
            {
                return _called = "UnrestrictedMethod";
            }

            public string Permitted()
            {
                return _called = "Permitted";
            }

            public string NotPermitted()
            {
                return _called = "NotPermitted";
            }

            [TestPermission(SecurityAction.Demand, Permitted=false)]
            public string LocallyProtected()
            {
                return _called = "LocallyProtected";
            }

            #endregion
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeNullsInputTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();

                target.Invoke(null, null);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeGetNextNullInputTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();

                target.Invoke(
                    new MethodInvocationMock(
                        new Tested(),
                        typeof(ITested).GetMethod("UnprotectedMethod"),
                        new ParameterCollection(
                                new object[0],
                                new ParameterInfo[0],
                                p => true)),
                        null);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeGetMethodInvocationNullInputTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("UnprotectedMethod"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                target.Invoke(null, () => call.InvokeNext);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeUnprotectedTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("UnprotectedMethod"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                var returnValue = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual(1, call.TimesNextCalled);
                Assert.AreEqual("UnprotectedMethod", tested.Called);
                Assert.AreEqual("UnprotectedMethod", returnValue.ReturnValue);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeUnrestrictedTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("UnrestrictedMethod"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                var returnValue = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual(1, call.TimesNextCalled);
                Assert.AreEqual("UnrestrictedMethod", tested.Called);
                Assert.AreEqual("UnrestrictedMethod", returnValue.ReturnValue);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokePermittedTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Permitted"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                var returnValue = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual(1, call.TimesNextCalled);
                Assert.AreEqual("Permitted", tested.Called);
                Assert.AreEqual("Permitted", returnValue.ReturnValue);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeNotPermittedTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("NotPermitted"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                var returnValue = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual(0, call.TimesNextCalled);
                Assert.IsNull(tested.Called);
                Assert.IsNotNull(returnValue.Exception);
                Assert.AreEqual(typeof(SecurityException), returnValue.Exception.GetType());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeLocallyProtectedTest()
        {
            try
            {
                var target = new CodeAccessSecurityCallHandler();
                var tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("LocallyProtected"),
                                    new ParameterCollection(
                                            new object[0],
                                            new ParameterInfo[0],
                                            p => true));

                var returnValue = target.Invoke(call, () => call.InvokeNext);

                Assert.IsNotNull(returnValue.Exception);
                Assert.IsTrue(returnValue.Exception.InnerException is SecurityException);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }
    }
}
